﻿using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System;
using System.Collections;

namespace PickGold.Base.OCR.QRCode.Render
{
	/// <summary>
	/// 
	/// </summary>
	public class GraphicsRenderer
	{
		private Brush _DarkBrush;
		private Brush _LightBrush;
		private ISizeCalculation _Size;

		/// <summary>
		/// Initialize Renderer. Default brushes will be black and white.
		/// </summary>
		/// <param name="size">The way of calculate Size</param>
		public GraphicsRenderer(ISizeCalculation size) : this(size, Brushes.White, Brushes.Black) { }

		/// <summary>
		/// Initialize Renderer. Default brushes will be black and white.
		/// </summary>
		/// <param name="size">The way of calculate Size</param>
		/// <param name="lightBrush"></param>
		public GraphicsRenderer(ISizeCalculation size, Brush lightBrush) : this(size, Brushes.White, null) { }

		/// <summary>
		/// Initialize Renderer
		/// </summary>
		/// <param name="size">The way of calculate Size</param>
		/// <param name="lightBrush"></param>
		/// <param name="darkBrush"></param>
		public GraphicsRenderer(ISizeCalculation size, Brush lightBrush, Brush darkBrush)
		{
			this._Size = size;
			this._LightBrush = lightBrush;
			this._DarkBrush = darkBrush;
		}

		/// <summary>
		/// Drawing Bitmatrix to winform graphics.
		/// Default position will be 0, 0
		/// </summary>
		/// <param name="graphics"></param>
		/// <param name="matrix">Draw background only for null matrix</param>
		/// <exception cref="ArgumentNullException">DarkBrush or LightBrush is null</exception>
		public int Draw(Graphics graphics, BitMatrix matrix)
		{
			return this.Draw(graphics, matrix, Point.Empty);
		}

		/// <summary>
		/// Drawing Bitmatrix to winform graphics.
		/// </summary>
		/// <param name="graphics"></param>
		/// <param name="matrix">Draw background only for null matrix</param>
		/// <param name="offset"></param>
		/// <exception cref="ArgumentNullException">DarkBrush or LightBrush is null</exception>
		public int Draw(Graphics graphics, BitMatrix matrix, Point offset)
		{
			if (matrix == null)
				return 0;

			var width = matrix.Width;
			var size = this._Size.GetSize(width);
			if (size.ModuleSize == 0)
				return 0;

			var pad = (size.CodeSize - (size.ModuleSize * width)) / 2;
			var bs = matrix.FillArray();
			var b = this._LightBrush;
			if (b != null)
			{
				graphics.FillRectangle(b, offset.X, offset.Y, size.CodeSize, pad);
				graphics.FillRectangle(b, offset.X, offset.Y + size.CodeSize - pad, size.CodeSize, pad);
				graphics.FillRectangle(b, offset.X, offset.Y, pad, size.CodeSize);
				graphics.FillRectangle(b, offset.X + size.CodeSize - pad, offset.Y, pad, size.CodeSize);
			}
			for (var iy = 0; iy < width; iy++)
			{
				var h = size.ModuleSize;
				var y = iy * h + pad + offset.Y;
				for (var ix = 0; ix < width; ix++)
				{
					var w = h;
					var x = ix * w + pad + offset.X;
					if (bs[iy * width + ix])// if (bs[ix, iy])
						b = this._DarkBrush;
					else
						b = this._LightBrush;
					if (b != null)
						graphics.FillRectangle(b, x, y, w, h);
				}
			}
			return width;
		}
		private int DrawBL(Graphics graphics, BitMatrix matrix, Point offset)
		{
			if (matrix == null)
				return 0;

			var width = matrix.Width;
			var size = this._Size.GetSize(width);
			if (size.ModuleSize == 0)
				return 0;

			var pad = (size.CodeSize - (size.ModuleSize * width)) / 2;
			var pre = int.MinValue;
			graphics.FillRectangle(this._LightBrush, offset.X, offset.Y, size.CodeSize, size.CodeSize);
			for (var iy = 0; iy < width; iy++)
			{
				for (var ix = 0; ix < width; ix++)
				{
					var x = size.ModuleSize;
					var y = size.ModuleSize;
					var w = size.ModuleSize;
					var h = size.ModuleSize;
					if (matrix[ix, iy])
					{
						//Set start point if preX == -1
						if (pre == int.MinValue)
							pre = ix;
						//If this is last module in that row. Draw rectangle
						if (ix == width - 1)
						{
							x = pre * x + pad + offset.X;
							y = iy * y + pad + offset.Y;
							w = (ix - pre + 1) * w;
							graphics.FillRectangle(this._DarkBrush, x, y, w, h);
							pre = int.MinValue;
						}
					}
					else if (pre != int.MinValue)
					{
						//Here will be first light module after sequence of dark module.
						//Draw previews sequence of dark Module
						x = pre * x + pad + offset.X;
						y = iy * y + pad + offset.Y;
						w = (ix - pre) * size.ModuleSize;
						graphics.FillRectangle(this._DarkBrush, x, y, w, h);
						pre = int.MinValue;
					}
				}
			}
			return width;
		}

		/// <summary>
		/// Saves QrCode Image to specified stream in the specified format
		/// </summary>
		/// <exception cref="ArgumentNullException">Stream or Format is null</exception>
		/// <exception cref="ExternalException">The image was saved with the wrong image format</exception>
		/// <remarks>You should avoid saving an image to the same stream that was used to construct. Doing so might damage the stream
		/// If any additional data has been written to the stream before saving the image, the image data in the stream will be corrupted</remarks>
		public void WriteToStream(BitMatrix matrix, ImageFormat imageFormat, Stream stream, Point DPI)
		{
			if (imageFormat == ImageFormat.Emf || imageFormat == ImageFormat.Wmf)
			{
				this.CreateMetaFile(matrix, stream);
				return;
			}

			if (imageFormat == ImageFormat.Exif
				|| imageFormat == ImageFormat.Icon
				|| imageFormat == ImageFormat.MemoryBmp)
				return;

			var size = this._Size.GetSize(matrix == null ? 21 : matrix.Width);
			using (var bitmap = new Bitmap(size.CodeSize, size.CodeSize))
			{
				if (DPI != Point.Empty && (DPI.X != 96 || DPI.Y != 96))
					bitmap.SetResolution(DPI.X, DPI.Y);
				using (var graphics = Graphics.FromImage(bitmap))
					this.DrawBL(graphics, matrix, Point.Empty);
				bitmap.Save(stream, imageFormat);
			}
		}

		/// <summary>
		/// Saves QrCode Image to specified stream in the specified format
		/// </summary>
		/// <exception cref="ArgumentNullException">Stream or Format is null</exception>
		/// <exception cref="ExternalException">The image was saved with the wrong image format</exception>
		/// <remarks>You should avoid saving an image to the same stream that was used to construct. Doing so might damage the stream
		/// If any additional data has been written to the stream before saving the image, the image data in the stream will be corrupted</remarks>
		public void WriteToStream(BitMatrix matrix, ImageFormat imageFormat, Stream stream)
		{
			this.WriteToStream(matrix, imageFormat, stream, Point.Empty);
		}

		/// <summary>
		/// Using MetaFile Class to create metafile. 
		/// temp control create to use as object to get temp graphics for Hdc. 
		/// Drawing on the metaGraphics will record as vector metaFile. 
		/// </summary>
		private void CreateMetaFile(BitMatrix matrix, Stream stream)
		{
			using (var mf = new Metafile(stream))
			using (var gg = Graphics.FromImage(mf))
				this.Draw(gg, matrix);
		}

		/// <summary>
		/// DarkBrush for drawing Dark module of QrCode
		/// </summary>
		public Brush DarkBrush
		{
			set
			{
				this._DarkBrush = value;
			}
			get
			{
				return this._DarkBrush;
			}
		}

		/// <summary>
		/// LightBrush for drawing Light module and QuietZone of QrCode
		/// </summary>
		public Brush LightBrush
		{
			set
			{
				this._LightBrush = value;
			}
			get
			{
				return this._LightBrush;
			}
		}

		/// <summary>
		/// ISizeCalculation for the way to calculate QrCode's pixel size.
		/// Ex for ISizeCalculation:FixedCodeSize, FixedModuleSize
		/// </summary>
		public ISizeCalculation SizeCalculator
		{
			set
			{
				this._Size = value;
			}
			get
			{
				return this._Size;
			}
		}

		#region Render

		public static void Render(Stream output, string content, Point offset, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White, Brushes.Black);
			render.WriteToStream(QREncoder.Encode(content).Matrix, ImageFormat.Png, output, offset);
		}

		public static void Render(Stream output, string content, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White, Brushes.Black);
			render.WriteToStream(QREncoder.Encode(content).Matrix, ImageFormat.Png, output, Point.Empty);
		}

		public static void Render(Graphics output, string content, Point offset, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White);
			render.Draw(output, QREncoder.Encode(content).Matrix, offset);
		}

		public static void Render(Graphics output, string content, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White);
			render.Draw(output, QREncoder.Encode(content).Matrix, Point.Empty);
		}

		public static void Render(Image output, string content, Point offset, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White);
			using (var g = Graphics.FromImage(output))
				render.Draw(g, QREncoder.Encode(content).Matrix, offset);
		}

		public static void Render(Image output, string content, int moduleSize = 12, int quietZoneModules = 1)
		{
			var render = new GraphicsRenderer(new FixedModuleSize(moduleSize, quietZoneModules), Brushes.White);
			using (var g = Graphics.FromImage(output))
				render.Draw(g, QREncoder.Encode(content).Matrix, Point.Empty);
		}

		#endregion
	}
}
